﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGinnBPM.Rules.DSL;
using System.IO;
using NLog;

namespace NGinnBPM.Rules.TableDriven
{
    /// <summary>
    /// Rule engine using CSV files
    /// </summary>
    public class TableDrivenRuleEngine
    {
        /// <summary>
        /// Directory where rule tables (csvs) are stored
        /// </summary>
        public string BaseDirectory { get; set; }
        /// <summary>
        /// Temp directory for generating Boo files
        /// </summary>
        public string TempDirectory { get; set; }
        /// <summary>
        /// Encoding used with csv files
        /// </summary>
        public Encoding CsvEncoding { get; set; }
        /// <summary>
        /// CSV file separator (semicolon by default - Excel)
        /// </summary>
        public char CsvSeparator { get; set; }

        private Dictionary<string, RulesetFileInfo> _rulesetBuildTimes = new Dictionary<string, RulesetFileInfo>();
        private static Logger log = LogManager.GetCurrentClassLogger();
        private RuleEngine _re;

        private class RulesetFileInfo
        {
            public string CsvName { get; set; }
            public string BooName { get; set; }
            public DateTime LastUpdateTime { get; set; }
        }

        public TableDrivenRuleEngine()
        {
            CsvEncoding = Encoding.GetEncoding("windows-1250");
            string td = Guid.NewGuid().ToString("N");
            TempDirectory = Path.Combine(Path.GetTempPath(), td);
            log.Info("Temp directory is {0}", TempDirectory);
            _re = new RuleEngine { DetectFileModification = true, BaseDirectory = TempDirectory, EvalMode = RuleEngine.RuleEvalMode.FirstMatching };
            CsvSeparator = ';';
        }

        /// <summary>
        /// Evaluate rules
        /// </summary>
        /// <param name="ruleset">ruleset csv file name</param>
        /// <param name="input">input object (accessible by T alias in rules)</param>
        /// <param name="output">output object (accessible by R alias in rules)</param>
        public void EvalRules(string ruleset, object input, object output)
        {
            EnsureRuleScriptIsUpToDate(ruleset);
            RulesetFileInfo rfi;
            if (!_rulesetBuildTimes.TryGetValue(ruleset, out rfi))
                throw new Exception();
            _re.ExecuteRules(rfi.BooName, input, output);
        }

        private void EnsureRuleScriptIsUpToDate(string csvName)
        {
            if (!Directory.Exists(TempDirectory))
                Directory.CreateDirectory(TempDirectory);

            string filePath = Path.Combine(BaseDirectory, csvName);
            if (!File.Exists(filePath)) throw new FileNotFoundException("rule definition not found", filePath);

            string booName = Path.ChangeExtension(csvName, "boo");
            DateTime dt = File.GetLastWriteTime(filePath);
            RulesetFileInfo rfi;
            
            if (!_rulesetBuildTimes.TryGetValue(csvName, out rfi) || dt > rfi.LastUpdateTime)
            {
                log.Info("Building rules from csv: {0}", csvName);
                string outPath = Path.Combine(TempDirectory, booName);
                TableRulesetBuilder trb = new TableRulesetBuilder();
                RulesetDef rsd;
                using (StreamReader sr = new StreamReader(filePath, CsvEncoding))
                {
                    CsvReader cr = new CsvReader(sr);
                    cr.Separator = CsvSeparator;
                    rsd = trb.ReadRulesetFromCsv(cr);
                    rsd.Name = Path.GetFileNameWithoutExtension(csvName);
                }
                log.Info("Successfully loaded ruleset from {0}", filePath);
                RuleScriptGenerator rsg = new RuleScriptGenerator();
                rsg.GenerateScript(rsd, outPath);
                log.Info("Generated boo script for {0}: {1}", csvName, outPath);
                rfi = new RulesetFileInfo { BooName = booName, CsvName = csvName, LastUpdateTime = DateTime.Now };
                _rulesetBuildTimes.Remove(csvName);
                _rulesetBuildTimes[csvName] = rfi;
            }
            log.Info("Executing boo rules: {0}", booName);
        }
    }
}
